using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Jink.Core.Json.Converters.Extensibility;
using Jink.Core.Json.Converters.Shapes.Annotations;
using Jink.Core.Json.Converters.Shapes.Core;
using XenoGears.Functional;
using XenoGears.Reflection;
using XenoGears.Assertions;
using XenoGears.Reflection.Generics;
using XenoGears.Reflection.Shortcuts;
using XenoGears.Reflection.Simple;

namespace Jink.Core.Json.Converters.Shapes
{
    [JsonConverter]
    internal static class DefaultShapeProviders
    {
        [JsonPbagProvider(Weight = 0.6)]
        public static IJsonPbag TrivialJsonPbagProvider(IJsonPbag pbag)
        {
            return pbag;
        }

        [JsonPbagProvider(Weight = 0.5)]
        public static IJsonPbag DefaultJsonPbagProvider<T>(T pbag)
        {
            // todo. implement more sophisticated strategies
            // e.g.: something that supports non-public stuff, 
            // something that lets one choose between opt-in and opt-out...

            var fields = typeof(T).GetFields(BF.PublicInstance);
            var props = typeof(T).GetProperties(BF.PublicInstance).Where(p => p.CanRead && p.CanWrite);
            (fields.IsNotEmpty() || props.IsNotEmpty()).AssertTrue();

            var ftypes = fields.ToDictionary(f => f.Name, f => f.FieldType);
            var ptypes = props.ToDictionary(p => p.Name, p => p.PropertyType);
            return JsonPbag.ReadWrite(
                () => ftypes.Concat(ptypes).ToDictionary(), 
                name => pbag.Get(name),
                (name, value) => pbag.Set(name, value));
        }

        [JsonPbagProvider(Weight = 0.6)]
        public static IJsonSeq TrivialJsonSeqProvider(IJsonSeq seq)
        {
            return seq;
        }

        [JsonSeqProvider(Weight = 0.4)]
        public static IJsonSeq DefaultJsonSeqProvider_ReadOnly<T>(T seq)
        {
            var unambiguousEnum = typeof(T).SameMetadataToken(typeof(IEnumerable<>)) ? typeof(T) :
                typeof(T).GetInterfaces().Where(iface => iface.SameMetadataToken(typeof(IEnumerable<>))).SingleOrDefault2();
            var t_el = unambiguousEnum == null ? null : unambiguousEnum.XGetGenericArguments().Single();
            t_el.AssertNotNull();

            var m_getEnumerator = unambiguousEnum.GetMethod("GetEnumerator");
            Func<IEnumerator> getEnumerator = () => (IEnumerator)m_getEnumerator.Invoke(seq, null);
            return JsonSeq.ReadOnly(() => t_el, () => new Enumerable(getEnumerator));
        }

        [JsonSeqProvider(Weight = 0.5)]
        public static IJsonSeq DefaultJsonSeqProvider_ReadWrite<T>(T seq)
        {
            var readOnly = DefaultJsonSeqProvider_ReadOnly(seq);
            var m_add = typeof(T).GetMethods(BF.AllInstance).SingleOrDefault2(
                m => m.Name == "Add" && m.Paramc() == 1 && m.Params().AssertSingle() == readOnly.TElement);
            m_add.AssertNotNull();

            Action<Object> add = el => m_add.Invoke(seq, el.MkArray());
            return JsonSeq.ReadWrite(() => readOnly.TElement, () => readOnly.Elements, add);
        }

        private class Enumerable : IEnumerable
        {
            private readonly Func<IEnumerator> _getEnumerator;

            public Enumerable(Func<IEnumerator> getEnumerator)
            {
                _getEnumerator = getEnumerator;
            }

            public IEnumerator GetEnumerator()
            {
                return _getEnumerator();
            }
        }

        [JsonPbagProvider(Weight = 0.6)]
        public static IJsonMap TrivialJsonMapProvider(IJsonMap map)
        {
            return map;
        }

        [JsonMapProvider(Weight = 0.4)]
        public static IJsonMap DefaultJsonMapProvider_ReadOnly<T>(T map)
        {
            var unambiguousDict = typeof(T).SameMetadataToken(typeof(IDictionary<,>)) ? typeof(T) :
                typeof(T).GetInterfaces().Where(iface => iface.SameMetadataToken(typeof(IDictionary<,>))).SingleOrDefault2();
            var t_key = unambiguousDict == null ? null : unambiguousDict.XGetGenericArguments().FirstOrDefault();
            var t_value = unambiguousDict == null ? null : unambiguousDict.XGetGenericArguments().SecondOrDefault();
            (t_key != null && t_value != null).AssertTrue();

            var m_getEnumerator = unambiguousDict.GetMethod("GetEnumerator");
            Func<IEnumerator> getEnumerator = () => (IEnumerator)m_getEnumerator.Invoke(map, null);
            return JsonMap.ReadOnly(() => t_key, () => t_value, () => new DictionaryEnumerable(getEnumerator));
        }

        [JsonSeqProvider(Weight = 0.5)]
        public static IJsonSeq DefaultJsonMapProvider_ReadWrite<T>(T map)
        {
            var readOnly = DefaultJsonMapProvider_ReadOnly(map);
            var m_add = typeof(T).GetMethods(BF.AllInstance).SingleOrDefault2(
                m => m.Name == "Add" && m.Paramc() == 2 && m.Param(0) == readOnly.TKey && m.Param(1) == readOnly.TValue);
            m_add.AssertNotNull();

            Action<Object, Object> add = (key, value) => m_add.Invoke(map, new Object[]{key, value});
            return JsonMap.ReadWrite(() => readOnly.TKey, () => readOnly.TValue, () => readOnly.Elements, add);
        }

        private class DictionaryEnumerable : IEnumerable<DictionaryEntry>
        {
            private readonly Func<IEnumerator> _getEnumerator;

            public DictionaryEnumerable(Func<IEnumerator> getEnumerator)
            {
                _getEnumerator = getEnumerator;
            }

            private class DictionaryEnumerator : IEnumerator<DictionaryEntry>
            {
                private readonly IEnumerator _enumerator;

                public DictionaryEnumerator(IEnumerator enumerator)
                {
                    _enumerator = enumerator;
                }

                public void Dispose()
                {
                    if (_enumerator is IDisposable)
                    {
                        ((IDisposable)_enumerator).Dispose();
                    }
                }

                public bool MoveNext()
                {
                    return _enumerator.MoveNext();
                }

                public void Reset()
                {
                    _enumerator.Reset();
                }

                Object IEnumerator.Current { get { return Current; } }
                public DictionaryEntry Current
                {
                    get
                    {
                        var kvp = _enumerator.Current;
                        kvp.GetType().SameMetadataToken(typeof(KeyValuePair<,>)).AssertTrue();
                        return new DictionaryEntry(kvp.Get("Key"), kvp.Get("Value"));
                    }
                }
            }

            IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
            public IEnumerator<DictionaryEntry> GetEnumerator()
            {
                return new DictionaryEnumerator(_getEnumerator());
            }
        }
    }
}